package com.gr3g.dev.game.jme;

import com.jme3.app.SimpleApplication;
import com.jme3.font.BitmapText;
import com.jme3.input.KeyInput;
import com.jme3.input.controls.ActionListener;
import com.jme3.input.controls.KeyTrigger;
import com.jme3.light.DirectionalLight;
import com.jme3.material.Material;
import com.jme3.math.ColorRGBA;
import com.jme3.math.Vector3f;
import com.jme3.terrain.geomipmap.TerrainLodControl;
import com.jme3.terrain.geomipmap.TerrainQuad;
import com.jme3.terrain.geomipmap.lodcalc.DistanceLodCalculator;
import com.jme3.terrain.heightmap.AbstractHeightMap;
import com.jme3.terrain.heightmap.ImageBasedHeightMap;
import com.jme3.texture.Texture;
import com.jme3.texture.Texture.WrapMode;

/** Hello world! */
public final class MyGame extends SimpleApplication {

    private TerrainQuad terrain;
    private Material matRock;
    private Material matWire;
    boolean wireframe = false;
    boolean triPlanar = false;
    protected BitmapText hintText;
    private final float grassScale = 64;
    private final float dirtScale = 16;
    private final float rockScale = 128;

    public static void main(final String[] args) {
        final MyGame app = new MyGame();
        app.start();
    }

    @Override
    public void initialize() {
        super.initialize();
        this.loadHintText();
    }

    private void initTextures() {
        // TERRAIN TEXTURE material
        this.matRock = new Material(this.assetManager,
                "Common/MatDefs/Terrain/Terrain.j3md");
        this.matRock.setBoolean("useTriPlanarMapping", true);

        // ALPHA map (for splat textures)
        this.matRock.setTexture("Alpha", this.assetManager
                .loadTexture("Textures/Terrain/splat/alphamap.png"));

        // GRASS texture
        final Texture grass = this.assetManager
                .loadTexture("Textures/Terrain/splat/grass.jpg");
        grass.setWrap(WrapMode.Repeat);
        this.matRock.setTexture("Tex1", grass);
        this.matRock.setFloat("Tex1Scale", this.grassScale);

        // DIRT texture
        final Texture dirt = this.assetManager
                .loadTexture("Textures/Terrain/splat/dirt.jpg");
        dirt.setWrap(WrapMode.Repeat);
        this.matRock.setTexture("Tex2", dirt);
        this.matRock.setFloat("Tex2Scale", this.dirtScale);

        // ROCK texture
        final Texture rock = this.assetManager
                .loadTexture("Textures/Terrain/splat/road.jpg");
        rock.setWrap(WrapMode.Repeat);
        this.matRock.setTexture("Tex3", rock);
        this.matRock.setFloat("Tex3Scale", this.rockScale);

        // WIREFRAME material
        this.matWire = new Material(this.assetManager,
                "Common/MatDefs/Misc/Unshaded.j3md");
        this.matWire.getAdditionalRenderState().setWireframe(true);
        this.matWire.setColor("Color", ColorRGBA.Green);
    }

    private void initTerrain() {
        // CREATE HEIGHTMAP
        // HEIGHTMAP image (for the terrain heightmap)
        final Texture heightMapImage = this.assetManager
                .loadTexture("Textures/Terrain/splat/mountains128.png");
        AbstractHeightMap heightmap = null;
        try {
            heightmap = new ImageBasedHeightMap(heightMapImage.getImage(), 1f);
            heightmap.load();
        } catch (final Exception exc) {
            exc.printStackTrace();
        }
        /* Here we create the actual terrain. The tiles will be 33x33, and the
         * total size of the
         * terrain will be 129x129. It uses the heightmap we created to generate
         * the height values. */
        this.terrain = new TerrainQuad("terrain", 33, 129, heightmap
                .getHeightMap());
        final TerrainLodControl control = new TerrainLodControl(this.terrain,
                this.getCamera());
        control.setLodCalculator(new DistanceLodCalculator(33, 2.7f)); // patch
        // size, and a multiplier
        this.terrain.addControl(control);
        this.terrain.setMaterial(this.matRock);
        this.terrain.setLocalTranslation(0, -100, 0);
        this.terrain.setLocalScale(8f, 0.5f, 8f);
        this.rootNode.attachChild(this.terrain);
    }

    private void initLight() {
        final DirectionalLight light = new DirectionalLight();
        light.setDirection(new Vector3f(-0.5f, -1f, -0.5f).normalize());
        this.rootNode.addLight(light);
    }

    private void initCamera() {
        this.cam.setLocation(new Vector3f(0, 10, -10));
        this.cam.lookAtDirection(new Vector3f(0, -1.5f, -1).normalizeLocal(),
                Vector3f.UNIT_Y);
    }

    @Override
    public void simpleInitApp() {
        this.setupKeys();
        this.initTextures();
        this.initTerrain();
        this.initLight();
        this.initCamera();
    }

    public void loadHintText() {
        this.hintText = new BitmapText(this.guiFont, false);
        this.hintText.setSize(this.guiFont.getCharSet().getRenderedSize());
        this.hintText.setLocalTranslation(0, this.getCamera().getHeight(), 0);
        this.hintText
        .setText("Hit T to switch to wireframe,  P to switch to tri-planar texturing");
        this.guiNode.attachChild(this.hintText);
    }

    private void setupKeys() {
        this.flyCam.setMoveSpeed(50);
        this.inputManager.addMapping("wireframe",
                new KeyTrigger(KeyInput.KEY_T));
        this.inputManager.addListener(this.actionListener, "wireframe");
        this.inputManager.addMapping("triPlanar",
                new KeyTrigger(KeyInput.KEY_P));
        this.inputManager.addListener(this.actionListener, "triPlanar");
    }

    private final ActionListener actionListener = new ActionListener() {

        @Override
        public void onAction(
                final String name,
                final boolean pressed,
                final float tpf) {
            if (name.equals("wireframe") && !pressed) {
                MyGame.this.wireframe = !MyGame.this.wireframe;
                if (!MyGame.this.wireframe) {
                    MyGame.this.terrain.setMaterial(MyGame.this.matWire);
                } else {
                    MyGame.this.terrain.setMaterial(MyGame.this.matRock);
                }
            } else if (name.equals("triPlanar") && !pressed) {
                MyGame.this.triPlanar = !MyGame.this.triPlanar;
                if (MyGame.this.triPlanar) {
                    MyGame.this.matRock.setBoolean("useTriPlanarMapping", true);
                    // planar textures don't use the mesh's texture coordinates
                    // but real world coordinates,
                    // so we need to convert these texture coordinate scales
                    // into real world scales so it looks
                    // the same when we switch to/from tr-planar mode
                    MyGame.this.matRock.setFloat("Tex1Scale",
                            1f / (512f / MyGame.this.grassScale));
                    MyGame.this.matRock.setFloat("Tex2Scale",
                            1f / (512f / MyGame.this.dirtScale));
                    MyGame.this.matRock.setFloat("Tex3Scale",
                            1f / (512f / MyGame.this.rockScale));
                } else {
                    MyGame.this.matRock
                    .setBoolean("useTriPlanarMapping", false);
                    MyGame.this.matRock.setFloat("Tex1Scale",
                            MyGame.this.grassScale);
                    MyGame.this.matRock.setFloat("Tex2Scale",
                            MyGame.this.dirtScale);
                    MyGame.this.matRock.setFloat("Tex3Scale",
                            MyGame.this.rockScale);
                }
            }
        }
    };
}
